#pragma once


#define ARRAY_GRANUL 128

template<class T> class CEX Array:public Object
{
	// Buffer holding the array data
	Buffer _buf;

	// Number of elements actually in the array
	int _size;

	// Number of allocated elements
	int _alloc;

public:
	
	virtual const char* __getName(){return "Array";}
	

	// Constructor
	Array()
	{
		_size = 0;
		_alloc = 0;
	}

	// Destructor
	~Array()
	{
		#ifdef PIX5DEBUG
			// Add a debug notice of an array has still some elements in it
			if(_size)
				Debug::write("Array of type %s has %i uncleared elements\n",typeid(T).name(),_size);
		#endif
	}

	// Copy constructor
	Array(const Array& o)
	{
		// Copy the array
		_buf = o._buf;
		_size = o._size;
		_alloc = o._alloc;
	}

	// Insert a new element at the specified position
	// ele: New element to insert
	// index: Index the new element should have
	// NOTE: If any elements are at 'index' or after, they will be pushed to the right
	void insert(const T& ele,int index)
	{
		ASSERT(index>=0 && index<=_size,"Index out of range")
		
		// Enlarge the array ?
		if(_size==_alloc)
		{
			// Yes, enlarge by the granularity
			_alloc += ARRAY_GRANUL;
			_buf.resize(_alloc*sizeof(T),true);
		}
	
		if(index<_size)
		{
			// Push some elements to the right to insert the new one in the middle
			T* ptr = ((T*)(char*)_buf)+index;
			memmove(ptr+1,ptr,(_size-index)*sizeof(T));
		}

		// Set the new element
		((T*)(char*)_buf)[index] = ele;

		// Increase the size
		_size++;

	}

	// Add a new element at the end of the array
	// ele: Element to add
	// Returns: The index of the added element
	int add(const T& ele)
	{
		// Simply insert at the end
		insert(ele,_size);

		// Return the last index
		return _size-1;
	}

	// Remove an element from the array
	// index: Element index to remove
	// NOTE: If there are elements after 'index', the will be pushed to the left
	void remove(int index)
	{
		ASSERT(index>=0 && index<_size,"Index out of range")

		if(index<(_size-1))
		{
			// Push to the left
			T* ptr = ((T*)(char*)_buf)+index;
			memmove(ptr,ptr+1,(_size-index-1)*sizeof(T));
		}

		// Decrease the size
		_size--;
	}

	// Remove an element from the array and delete it
	// index: Element index to remove
	// NOTE: If there are elements after 'index', the will be pushed to the left
	void removeDelete(int index)
	{
		ASSERT(index>=0 && index<_size,"Index out of range")

		// Delete the element
		T* ptr = ((T*)(char*)_buf)+index;
		delete *ptr;

		if(index<(_size-1))
		{
			// Push left
			memmove(ptr,ptr+1,(_size-index-1)*sizeof(T));
		}

		// Decrease the size
		_size--;
	}

	// Clear the array
	void clear()
	{
		// Clear everything
		_buf.clear();
		_size = 0;
		_alloc = 0;
	}

	// Clear the array and delete all the elements
	void clearDelete()
	{
		// Delete the elements from right to left, in case an error occurs
		T* ptr = ((T*)(char*)_buf)+_size-1;

		while(_size)
		{
			// Delete the element
			delete *(ptr--);
			_size--;
		}

		// Clear the buffer
		clear();
	}

	// Find an element within the array
	// ele: Element to look for
	// Returns: The index of the first occurence of 'ele', or -1 if not found
	// NOTE: A simple left-to-right search is performed
	int find(const T& ele) const
	{
		// Look for the element
		T* ptr = (T*)(char*)_buf;

		for(int a=0;a<_size;a++)
			if(*(ptr++)==ele)
				// Found it
				return a;

		// Not found
		return -1;
	}

	// Set the value of an element
	// index: Index of the element to set
	// ele: New value to set
	void set(int index,const T& ele)
	{
		ASSERT(index>=0 && index<_size,"Index out of range")

		// Set the new element value
		((T*)(char*)_buf)[index] = ele;
	}

	// Return the size of the array, in elements
	int getSize() const
	{
		return _size;
	}
	
	// Copy another array
	// o: Array to copy
	Array<T>& operator=(const Array<T>& o)
	{
		// Copy the whole buffer integrally
		_buf = o._buf;
		_size = o._size;
		_alloc = o._alloc;

		return *this;
	}

	// Array operator
	// index: Index of the element to retrieve
	// Returns: The element at 'index'
	T operator[](int index) const
	{
		ASSERT(index>=0 && index<_size,"Index out of range")

		// Return the requested element
		return ((T*)(char*)_buf)[index];
	}

	// Split the array in two
	// index: Index where to begin the split
	// o: Array that will recieve the right-most part of the split
	// NOTE: Any previous data in 'o' will be lost
	void split(int index,Array<T>& o)
	{
		ASSERT(index>=0 && index<_size,"Index out of range")

		// Resize 'o' to fit all our elements starting at 'index' until the end of the array
		o._alloc = _size - index;
		o._size = o._alloc;
		o._buf.resize(o._alloc*sizeof(T),false);

		// Copy the elements
		memcpy((char*)o._buf,((char*)_buf)+index*sizeof(T),o._size*sizeof(T));

		// Shrink ourselves
		_size = index;
	}

	// Merge an array into ourself
	// o: Array to merge with
	// NOTE: All the elements of 'o' will be appended at the end of our own data
	void merge(const Array<T>& o)
	{
		// Resize the buffer to fit all the elements of 'o'
		if(_size+o._size>_alloc)
		{
			_alloc = _size+o._size;
			_buf.resize(_alloc*sizeof(T),true);
		}

		// Copy the second array's data at the end of our own
		memcpy(((char*)_buf)+_size*sizeof(T),(char*)o._buf,o._size*sizeof(T));

		// Increase our size
		_size += o._size;
	}

};

